package com.itxixi.domain.strategy.service.armory;

import com.itxixi.domain.strategy.model.entity.StrategyAwardEntity;
import com.itxixi.domain.strategy.model.entity.StrategyEntity;
import com.itxixi.domain.strategy.model.entity.StrategyRuleEntity;
import com.itxixi.domain.strategy.repository.IStrategyRepository;
import com.itxixi.types.common.Constants;
import com.itxixi.types.enums.ResponseCode;
import com.itxixi.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

/**
 * @author Pyx 2024/10/23
 * @description 策略装配库（兵工厂） 负责初始化策略计算
 * @create 2024-10-23 下午5:59
 */
@Slf4j
@Service
public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch{

    @Resource
    private IStrategyRepository strategyRepository;//当前do中对应的仓储接口

    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {

        //1.查询策略配置  DO                                //当前do中对应仓储接口中的方法 通过PO得到DO entity
                                                            //1.到IStrategyAwardDao 根据strategyId100001 查找StrategyAward实体
        List<StrategyAwardEntity> strategyAwardEntities =  strategyRepository.queryStrategyAwardList(strategyId);
        assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntities);//第一次装配  装配对应的策略id下所有的策略实体

        // 2. 权重策略配置 - 适用于 rule_weight 权重规则配置
                                                        //2.到IStrategyDao中根据strategyId查找Strategy实体  以得到rulemodel字段
        StrategyEntity strategyEntity = strategyRepository.queryStrategyEntityByStrategyId(strategyId);
        String ruleWeight = strategyEntity.getRuleWeight();//实体对应的自定义方法
        if (null == ruleWeight) return true;//判断Strategy实体的 ruleModel字段 是否含有 rule_weight

        //如果有rule_weight
                             //3.到IStrategyRuleDao 中根据strategyId 以及ruleModel含有的 ruleWeight 字段（必须有 因为if以及判断过了）
                                        // 得到strategyRuleEntity实体 以得到ruleValue
        StrategyRuleEntity strategyRuleEntity = strategyRepository.queryStrategyRule(strategyId, ruleWeight);
        if (null == strategyRuleEntity) {//就是有ruleweight字段但是没有配置 4000：101102103
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }                                                       ////实体对应的自定义方法
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
        //4000    101 102 103 104

        Set<String> keys = ruleWeightValueMap.keySet();//key：4000 value：101 102 103 ...
        for (String key : keys) {
            //101 102 103 ...
            List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);

            //根据配置的权重 筛选 已经查找到的StrategyAward实体  浅拷贝
            ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
            strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
            assembleLotteryStrategy(String.valueOf(strategyId).concat("_").concat(key), strategyAwardEntitiesClone);
                                    //装配Map.size()个新的概率查找表 在这里面找totalAwardRate rateRange   key为id_key(strategyId_4000)

         }
        return true;
    }
            //仓储层没有非空判断 会一直重新覆盖
    private void assembleLotteryStrategy(String key,  List<StrategyAwardEntity> strategyAwardEntities) {

        //1.获取最小概率值
        BigDecimal minAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        //2.获取概率总和
        BigDecimal totalAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        //3. 用 1 % 0.0001 获取概率范围 百分位 千分位 万分位  总概率除最小概率 和 strategyAwardSearchRateTables.size()（为不同奖项）还是不一样的
        BigDecimal rateRange = totalAwardRate.divide(minAwardRate,0, RoundingMode.CEILING);//向上取整  如果超过1了也向上取整

        //4. 寻找对应的table
        ArrayList<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());//rateRange.intValue()会截断小数部分 数字会变小
        //即使初始容量不足以存储所有元素，ArrayList也会在需要时自动进行扩容，以确保可以继续添加元素。

        for (StrategyAwardEntity strategyAwardEntity : strategyAwardEntities) {
            Integer awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();    //  0.2 0.25 0.25 0.3
            //计算每个概率值需要存放到查找表的数量，循环填充  向上取整 比如有四个奖项 minrate为0.2 rateRange = 1/0.2 = 5
            for(int i = 0; i < rateRange.multiply(awardRate).setScale(0,RoundingMode.CEILING).intValue(); i++){
                strategyAwardSearchRateTables.add(awardId);         //所以tables ：0.2 * 5 = 1   1.25 1.25 1.5   7个位置
                //相当于每个概率占多少个空间                                 // 101 102 102 103 103 104 104 会有两个超过索引
            }
//            for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {

        }

        //5.乱序
        Collections.shuffle(strategyAwardSearchRateTables);

        //6. 用map存储乱序后的表
        HashMap<Integer,Integer> shuffleStrategyAwardSearchRateTables = new HashMap<>();
        //意思就是要提前放好key 这就是为什么需要概率范围
        for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {            //多个数值（random）对应一个概率 可以通过redis get（key）得到value
            shuffleStrategyAwardSearchRateTables.put(i,strategyAwardSearchRateTables.get(i));//key为概率范围中的数值 value对应一个奖品id awardId
        }
        //分布式应用 rateRange不一定当前的值在当前的机器下
//        strategyRepository.storeStrategyAwardSearchRateTables(strategyId,rateRange,shuffleStrategyAwardSearchRateTables);
        strategyRepository.storeStrategyAwardSearchRateTables(key,shuffleStrategyAwardSearchRateTables.size(),shuffleStrategyAwardSearchRateTables);
        //存入redis 1概率范围 和 2hashmap查找表

    }



    @Override
    public Integer getRandomAwardId(Long strategyId) {          //storeStrategyAwardSearchRateTables
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。

        return getRandomAwardId(String.valueOf(strategyId));
        // 获取 strategyId 对应的概率范围
//        int rateRange = strategyRepository.getRateRange(strategyId);//得到storeStrategyAwardSearchRateTables中对应key（strategyId）的 概率范围
//
//        //得到key strategyId 对应的查找表中field为rateKey的value awardid
//        return strategyRepository.getStrategyAwardAssemble(String.valueOf(strategyId),new SecureRandom().nextInt(rateRange));
    }   //return AwardId Integer类型                            // 获取概率范围中的随机数  //101 102 103 104 105
                                        //strategyId        对应总的装配表
//---------------------------------------------------------------------------------------------------

    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String key = String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(ruleWeightValue);
        return getRandomAwardId(key);   //strategyId_4000    对应4000的装配表
    }

    @Override
    public Integer getRandomAwardId(String key) {
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。
        int rateRange = strategyRepository.getRateRange(key);
        // 通过生成的随机值，获取概率值奖品查找表的结果
        return strategyRepository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
    }

//---------------------------------------------------------------------------------------------------





}
